﻿using System;
using System.Data;
using System.Text;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
    #region Documentation.
    /// <summary>
    /// Contains read-only database column metadata and methods.
    /// </summary>
    /// <remarks><para>Contains metadata for the database columns it represents.</para>
    /// <para><b>SealedDbColumn</b> class implements <see cref="IDbColumn"/> interface in a more efficient manner than
    /// obsoleted <b>DbColumn</b> class. It implements only setters mandated by <see cref="IDbColumn"/> interface. All other 
    /// properties are read-only and guaranted that no attempt will be made to modify underlying data stored in
    /// <see cref="IDbColumnConfiguration"/> object. This makes it possible to use a single static <b>IDbColumnConfiguration</b>
    /// object that will be used by all <b>SealedDbColumn</b> instances which represent the same database field.
    /// Latest version of <b>BizElements Generator</b> implements code generation that supports the described pattern
    /// which significantly optimizes memory usage.</para>
    /// <para>Setters mandated by <b>IDbTable</b> interface are implemented in a very conservative manner. Only <see cref="IDbColumn.Table"/> 
    /// property affects internal state of object. Additionaly, <see cref="IDbColumn.Table"/> property may
    /// be used only once. If it is already set then any further attemps to change it are ignored.</para>
    /// <para>When using singleton design pattern for <b>IDbColumnConfiguration</b> and <b>IDbTable</b> objects, each new <b>SealedDbColumn</b> 
    /// instance will only consume additional <b>8 bytes</b>. This is significantly less than the <b>DbColumn</b> column class where each instance 
    /// had a variable size averaging between 100 and 200 bytes in most cases.</para>
    /// </remarks>
    /// <example>
    /// The following example uses generated table and column metadata to select data from tables Territories and Region.
    /// <code>
    /// public DataTable SelectTerritoriesAndRegions()
    /// {			
    /// 	// SELECT Territories.TerritoryDescription, Region.RegionDescription
    /// 	// FROM Territories
    /// 	//		INNER JOIN Region ON Territories.RegionID = Region.RegionID
    /// 
    /// 	TerritoriesMeta territories = new TerritoriesMeta();
    /// 	RegionMeta regions = new RegionMeta();			
    /// 
    /// 	SelectStatement select = new SelectStatement(territories);
    /// 	select.SelectList.Add(territories.TerritoryDescription);
    /// 	select.SelectList.Add(regions.RegionDescription);
    /// 	select.Relations.Add(new DbRelation(regions, territories, territories.RegionID));
    /// 
    /// 	DataTable territoryAndRegionNames = select.Execute();
    /// 	return territoryAndRegionNames;
    /// }
    /// </code>
    /// </example>
    /// <seealso cref="DbColumnExtensions"/><seealso cref="IDbTable"/>
    #endregion
    [Serializable]
    public sealed class SealedDbColumn : IDbColumn
    {
        #region Constants and members.

        // Separator between table and column name in prefixed alias. Eg: Country_IdCountry.
        const string PrefixedAliasSeparator = "_";
        IDbColumnConfiguration immutableProperties;
        IDbTable table = null;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the DbColumn class.
        /// </summary>
        /// <param name="immutableProperties">Provides values for immutable properties.</param>
        /// <param name="table">Table or view to which this column belongs to.</param>
        public SealedDbColumn(IDbColumnConfiguration immutableProperties, IDbTable table)
        {
            this.immutableProperties = immutableProperties;
            this.table = table;
        }

        #endregion

        #region GetPrefixedAlias.

        /// <summary>
        /// Computes a string which is a combination of prefix (usually table name) and column alias.
        /// Eg: Id -> MyTable_Id
        /// </summary>
        /// <returns>Prefixed alias.</returns>
        public string GetPrefixedAlias()
        {
            return this.Prefix + this.ColumnName;
        }

        #endregion

        #region Mutable (table dependent) properties and getters.

        /// <summary>
        /// Gets or sets <see cref="IDbTable"/> object to which this column belongs to.
        /// </summary>
        /// <remarks>Table may be only set once. If the property is already set then further attempts to change it are ignored.</remarks>
        public IDbTable Table
        {
            get { return this.table; }
            set
            {
                if (this.table == null)
                    this.table = value;
            }
        }

        /// <summary>
        /// Gets alias or column name.
        /// </summary>
        /// <remarks><para>Alias is equal to column name, unless parent table specifies that a prefixed alias should be generated.
        /// If table's <see cref="IDbTable.ColumnAliasesArePrefixed"/> property is set to <b>true</b> then prefixed alias
        /// as generated by <see cref="GetPrefixedAlias()"/> method is returned.</para></remarks>
        public string Alias
        {
            get
            {
                bool shouldReturnPrefixedAlias = (this.table != null) && this.table.ColumnAliasesArePrefixed;
                return (shouldReturnPrefixedAlias) ? GetPrefixedAlias() : this.ColumnName;
            }
        }

        /// <summary>
        /// Gets alias prefix. Eg: "MyTable_". Empty string if parent table is not set.
        /// </summary>
        public string Prefix
        {
            get
            {
                if (this.table != null)
                    return CleanTableAlias(this.table.Alias) + PrefixedAliasSeparator;
                else
                    return "";
            }
        }

        private static string CleanTableAlias(string alias)
        {
            return alias.Replace("[", "").Replace("]", "").Replace(" ", "_");
        }

        #endregion

        #region Immutable properties.

        /// <summary>
        /// Gets the <see cref="DbType"/> of the column.
        /// Changes of this property affect the other type properties.
        /// </summary>
        public DbType DbType
        {
            get { return this.immutableProperties.DbType; }
        }

        /// <summary>
        /// Gets the value that indicates whether null values are allowed in this column.
        /// </summary>
        public bool IsNullable
        {
            get { return this.immutableProperties.IsNullable; }
        }

        /// <summary>
        /// Gets the value that indicates whether the column automatically increments the value for new rows added to the table.
        /// </summary>
        public bool AutoIncrement
        {
            get { return this.immutableProperties.AutoIncrement; }
        }

        /// <summary>
        /// Gets the name of the column.
        /// </summary>
        public string ColumnName
        {
            get { return this.immutableProperties.ColumnName; }
        }

        /// <summary>
        /// Gets .NET type of data stored in the column.
        /// Changes of this property affect the database type property.
        /// </summary>
        public Type DataType
        {
            get { return this.immutableProperties.DataType; }
        }

        /// <summary>
        /// Gets the default value for the column when creating new rows.
        /// </summary>
        public object DefaultValue
        {
            get { return this.immutableProperties.DefaultValue; }
        }

        /// <summary>
        /// Gets the maximum length of a text column.
        /// Has no effect it it is not a text column.
        /// </summary>
        public int MaxLengthIfText
        {
            get { return this.immutableProperties.MaxLengthIfText; }
        }

        /// <summary>
        /// Gets the position of the column in the database table/view.
        /// </summary>
        public int Ordinal
        {
            get { return this.immutableProperties.Ordinal; }
        }

        /// <summary>
        /// Gets the value that indicates whether the column is a part of the primary key.
        /// </summary>
        public bool IsPrimaryKeyPart
        {
            get { return this.immutableProperties.IsPrimaryKeyPart; }
        }

        /// <summary>
        /// Gets the value that indicates whether the column is a part of a foreign key.
        /// </summary>
        public bool IsForeignKeyPart
        {
            get { return this.immutableProperties.IsForeignKeyPart; }
        }

        /// <summary>
        /// Gets the value that indicates whether the column is a part of a unique constraint.
        /// </summary>
        public bool IsUniqueConstraintPart
        {
            get { return this.immutableProperties.IsUniqueConstraintPart; }
        }

        /// <summary>
        /// Gets the smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        public object MinValue
        {
            get { return this.immutableProperties.MinValue; }
        }

        /// <summary>
        /// Gets the smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        public object MaxValue
        {
            get { return this.immutableProperties.MaxValue; }
        }

        /// <summary>
        /// Gets the name of the generated entity class property mapped to this column.
        /// </summary>
        public string PropertyName
        {
            get { return this.immutableProperties.PropertyName; }
        }

        /// <summary>
        /// Gets the value which indicates whether the value of the field is automatically generated by
        /// database (eg. timestamp or computed fields in SQL Server). 
        /// If set to true then this field's value cannot be inserted or updated.
        /// </summary>
        public bool IsAutoGenerated
        {
            get { return this.immutableProperties.IsAutoGenerated; }
        }

        /// <summary>
        /// Gets the sequence used to increment values for this field.
        /// Must be <b>null</b> if AutoIncrement flag is set to false.
        /// </summary>
        public string SequenceName
        {
            get { return this.immutableProperties.SequenceName; }
        }

        /// <summary>Gets the collection of customized information associated with the column.</summary>
        /// <value>A IPropertyCollection with all custom information. <b>null</b> if no custom information is configured.</value>
        /// <remarks>The ExtendedProperties property lets you configure custom information for the column such as DBMS-specific properties.</remarks>
        public IPropertyCollection<string> ExtendedProperties
        {
            get { return this.immutableProperties.ExtendedProperties; }
        }

        #endregion
    }
}
